/*
 * Copyright 2008-2023 dexian.vip. All rights reserved.
 * Support: http://www.dexian.vip
 * License: http://www.dexian.vip/license
 */

package vip.dexian.admin.entity;

import vip.dexian.admin.enums.GenderEnum;
import vip.dexian.common.CommonAttributes;
import vip.dexian.core.base.bean.BaseEntity;
import vip.dexian.core.base.bean.UniqueEntity;
import vip.dexian.core.base.validation.group.Save;
import vip.dexian.core.base.validation.group.Update;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.PrePersist;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.Where;
import org.hibernate.validator.constraints.Length;

import java.io.Serial;
import java.time.LocalDateTime;
import java.util.Set;

/**
 * 管理员
 *
 * @author 挺好的 2023年06月05日 8:44
 */
@Entity
@Table (name = "br_admin_admin",
        indexes = {@Index (name = "is_valid", columnList = "isValid"), @Index (columnList = "username,uniqueTimestamp",
                unique = true), @Index (name = "mobile", columnList = "mobile,uniqueTimestamp", unique = true),})
@DynamicUpdate
@Where (clause = BaseEntity.IS_VALID_WHERE)
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@EqualsAndHashCode (callSuper = true)
public class Admin extends UniqueEntity <Long> {

    @Serial
    private static final long serialVersionUID = -5094885460790149380L;

    /**
     * 姓名
     */
    @NotBlank (message = "姓名不能为空")
    @Length (min = 2, max = 20, message = "姓名的长度应在{min}和{max}个字符之间")
    @Pattern (regexp = "^[_\\u4e00-\\u9fa5]+$", message = "姓名只允许下划线和中文的组合")
    @Comment ("姓名")
    @Column (nullable = false, length = 20)
    private String name;

    /**
     * 用户名
     */
    @NotBlank (message = "用户名不能为空", groups = Save.class)
    @Length (min = 2, max = 20, message = "用户名的长度应在{min}和{max}个字符之间 ", groups = Save.class)
    @Pattern (regexp = "^[0-9a-zA-Z_\\u4e00-\\u9fa5]+$", message = "用户名只允许数字、英文、下划线、中文的组合",
            groups = Save.class)
    @Comment ("用户名")
    @Column (nullable = false, length = 20, updatable = false)
    private String username;

    /**
     * 手机号码
     */
    @NotBlank (message = "手机号码不能为空")
    @Length (min = 11, max = 11, message = "手机号码的长度应在{min}和{max}个字符之间 ")
    @Comment ("手机号码")
    @Column (nullable = false, length = 20, columnDefinition = "VARCHAR(20)")
    private String mobile;

    /**
     * 密码，注意@Length中的max长度会覆盖@Column中的length，如果两者都指定的时候，要以@Length中的长度为准
     */
    @ToString.Exclude
    @NotBlank (message = "密码不能为空", groups = Save.class)
    @Length (min = 6, max = 60, message = "密码的长度应在{min}和{max}个字符之间", groups = Save.class)
    @Comment ("密码")
    @Column (nullable = false, length = 60)
    private String password;

    /**
     * 性别
     */
    @NotNull (message = "性别不能为空")
    @Comment ("性别")
    @Column (nullable = false)
    private GenderEnum gender;

    /**
     * 头像
     */
    @Length (max = 255, message = "头像的长度不可超过255个字符")
    @Comment ("头像")
    @Column
    private String avatar;

    /**
     * 是否启用
     */
    @NotNull (message = "账户是否启用不能为空")
    @Comment ("是否启用，1：是，0：否")
    @Column (nullable = false)
    private Boolean isEnabled;

    /**
     * 账户是否锁定
     */
    @NotNull (message = "账户是否锁定不能为空", groups = Update.class)
    @Comment ("是否未锁定，1：是->可用，0：否->不可用")
    @Column (nullable = false)
    private Boolean isAccountNonLocked;

    /**
     * 登录失败次数，该属性配合loginDate完成账户锁定解锁功能
     */
    @Comment ("登录失败次数")
    @Column (nullable = false)
    private Integer loginFailureCount;

    /**
     * 最后登录时间
     */
    @Comment ("最后登录时间")
    @Column (nullable = false)
    private LocalDateTime loginDate;

    /**
     * 最后登录IP。默认为： 0.0.0.0。 需要使用{@link vip.dexian.common.utils.IpToLongUtils}转换，仅支持ipv4
     */
    @Comment ("最后登录IP")
    @Column (nullable = false)
    private Long loginIp;

    /**
     * 账户登录失败的锁定时间
     */
    @Comment ("锁定时间")
    @Column
    private LocalDateTime lockedDate;

    /**
     * 部门id，此处不使用hibernate one to many，由手动进行设置和操作
     */
    @NotNull (message = "部门不能为空")
    @Column (nullable = false)
    @Comment ("部门")
    private Long departmentId;

    /**
     * 角色
     */
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @ManyToMany (fetch = FetchType.LAZY, targetEntity = Role.class)
    @JoinTable (name = "br_admin_admin_role",
            joinColumns = {@JoinColumn (name = "admin_id", foreignKey = @ForeignKey (name = "admin_id"))},
            inverseJoinColumns = {@JoinColumn (name = "role_id", foreignKey = @ForeignKey (name = "fk_admin_role_id"))})
    private Set <Role> roles;

    /**
     * 获取操作人员
     *
     * @return
     */
    @Transient
    @JsonIgnore
    public String getOperator () {
        return this.getName() + "(" + this.getMobile() + ")";
    }

    /**
     * 保存前的操作
     */
    @PrePersist
    public void prePersist () {
        this.setIsAccountNonLocked(true);
        this.setLoginFailureCount(0);
        this.setLoginIp(0L);
        // 最后登录时间，使用默认时间，该字段不可为空，后期可能参与搜索
        this.setLoginDate(CommonAttributes.DEFAULT_DATE);
        this.setLockedDate(null);
        this.setUniqueTimestamp(CommonAttributes.UniqueKeySetting.DEFAULT_UNIQUE_TIMESTAMP);
    }
}
